package com.zy.myFiltersFactory;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.google.gson.Gson;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.Data;
import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @program: cloud147
 * @description: 全局认证过滤器
 * @author: zy
 * @create: 2025-07-16 17:12
 */
@Component
@Log
public class GlobalAuthFilter implements GlobalFilter, Ordered {
    @Value("${token.secret}")
    private String secret;
    @Value("${token.expiration}")
    private long expiration; // in milliseconds

    private final AppProperties appProperties;
    private final AntPathMatcher pathMatcher = new AntPathMatcher();
    private final Gson gson = new Gson(); // Use a single Gson instance for efficiency

    public GlobalAuthFilter(AppProperties appProperties) {
        this.appProperties = appProperties;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        String path = request.getURI().getPath();
        log.info("Request path: " + path);

        // Check if the path is in the excludePaths (meaning it does NOT require token authentication)
        boolean isExcludedPath = false;
        for (String excludePath : appProperties.getExcludePaths()) {
            if (pathMatcher.match(excludePath, path)) {
                isExcludedPath = true;
                break;
            }
        }

        if (!isExcludedPath) {
            log.info("Path matched excludePaths: " + path + ". Bypassing authentication.");
            return chain.filter(exchange); // This path does not require authentication, directly proceed
        }

        // If not an excluded path, proceed with token authentication
        String token = request.getHeaders().getFirst("token");

        if (StringUtils.isEmpty(token)) {
            log.warning("No token found in request headers for path: " + path);
            response.setStatusCode(HttpStatus.UNAUTHORIZED); // Set HTTP status to 401
            Map<String, Object> map = new HashMap<>();
            map.put("code", 0);
            map.put("msg", "未登录或登录信息缺失");
            return writeResponse(response, map); // Use the refactored writeResponse
        } else {
            try {
                // Parse and validate token in one go
//                Jws<Claims> claimsJws = Jwts.parserBuilder()
//                        .setSigningKey(secret.getBytes())
//
//                        .build()
//                        .parseClaimsJws(token);
                // 确保这里也使用相同的 Base64 解码和 Key 生成逻辑
                Key signingKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret)); // <--- 修改这里

                Jws<Claims> jwsClaims = Jwts.parserBuilder()
                        .setSigningKey(signingKey) // 使用解码后生成的 Key
                        .build()
                        .parseClaimsJws(token);
                Claims claims=jwsClaims.getBody();

                Date expiration = claims.getExpiration();
                Date now = new Date();

                if (expiration != null && expiration.before(now)) {
                    log.warning("Token已过期！Path: " + path);
                    response.setStatusCode(HttpStatus.UNAUTHORIZED); // Set HTTP status to 401
                    Map<String, Object> map = new HashMap<>();
                    map.put("code", 0);
                    map.put("msg", "token已过期，请重新登录");
                    return writeResponse(response, map);
                }

                // Token is valid and not expired
                log.info("Token 有效 for path: " + path);
                log.info("用户名: " + claims.get("userName"));
                log.info("过期时间: " + expiration);
                log.info("产生时间: " + claims.get("iat"));

                // Extract user info and add to headers
                ServerHttpRequest.Builder builder = request.mutate();
                // Ensure claims are extracted as appropriate types
                Long userId = claims.get("userId", Long.class); // Assuming "id" claim is String, adjust if Long/Integer
                String username = claims.get("userName", String.class);
                Object rolesObj = claims.get("roles"); // Roles can be String or List<String>
                String roles = null;
                if (rolesObj instanceof List) {
                    roles = String.join(",", (List<String>) rolesObj);
                } else if (rolesObj != null) {
                    roles = rolesObj.toString();
                }

                if (userId != null) {
                    builder.header("X-User-ID", userId.toString());
                }
                if (username != null) {
                    builder.header("X-User-Name", username);
                }
                if (roles != null && !roles.isEmpty()) {
                    builder.header("X-User-Roles", roles);
                }

                ServerHttpRequest newRequest = builder.build();
                ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
                return chain.filter(newExchange); // Proceed with the modified request
            } catch (ExpiredJwtException e) {
                // This catch block is technically redundant if `if (expiration != null && expiration.before(now))` handles it.
                // But it's good for explicit handling if the `parseClaimsJws` itself throws it directly.
                log.severe("Token 已过期 (ExpiredJwtException): " + path + ", Error: " + e.getMessage());
                response.setStatusCode(HttpStatus.UNAUTHORIZED); // Set HTTP status to 401
                Map<String, Object> map = new HashMap<>();
                map.put("code", 0);
                map.put("msg", "token已过期，请重新登录");
                return writeResponse(response, map);
            } catch (JwtException e) {
               e.printStackTrace();
                log.severe("Token 非法！(JwtException): " + path + ", Error: " + e.getMessage());
                response.setStatusCode(HttpStatus.FORBIDDEN); // Set HTTP status to 403
                Map<String, Object> map = new HashMap<>();
                map.put("code", 0);
                map.put("msg", "token无效或非法");
                return writeResponse(response, map);
            } catch (Exception e) {
                // Catch any other unexpected exceptions during token processing
                log.severe("处理Token时发生未知错误: " + path + ", Error: " + e.getMessage());
                response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); // 500 Internal Server Error
                Map<String, Object> map = new HashMap<>();
                map.put("code", 0);
                map.put("msg", "服务器内部错误，请稍后再试");
                return writeResponse(response, map);
            }
        }
    }

    /**
     * Helper method to write JSON response and set content type.
     * Renamed from 'response' to 'writeResponse' for clarity.
     * @param response ServerHttpResponse object
     * @param data The data object to be converted to JSON
     * @return Mono<Void> indicating the completion of response writing
     */
    private Mono<Void> writeResponse(ServerHttpResponse response, Object data) {
        response.getHeaders().add("Content-Type", "application/json;charset=utf-8");
        String resJson = gson.toJson(data); // Using the shared Gson instance
        DataBuffer dataBuffer = response.bufferFactory().wrap(resJson.getBytes());
        return response.writeWith(Flux.just(dataBuffer));
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE; // Ensure this filter runs first
    }
}

@Component
@ConfigurationProperties(prefix = "rights")
@Data
class AppProperties {
    private List<String> excludePaths;
}
